use nom::{
    branch::alt,
    bytes::complete::{tag, take_while},
    character::complete::{alpha1, char},
    combinator::map,
    delimited, many0,
    multi::{many0, many1},
    number::complete::double,
    sequence::{delimited, preceded, tuple},
    IResult, Parser,
};
use std::fmt::Debug;

pub type ParsedProgram<'a> = Vec<ParsedStatement<'a>>;

#[derive(Debug)]
pub enum ParsedStatement<'a> {
    Declaration(&'a str),
    InputOperation(&'a str),
    OutputOperation(ParsedExpr<'a>),
    Assignment(&'a str, ParsedExpr<'a>),
}
pub type ParsedExpr<'a> = (ParsedTerm<'a>, Vec<(ExprOperator, ParsedTerm<'a>)>);

pub type ParsedTerm<'a> = (ParsedFactor<'a>, Vec<(TermOperator, ParsedFactor<'a>)>);

#[derive(Debug)]
pub enum ParsedFactor<'a> {
    Literal(f64),
    Identifier(&'a str),
    SubExpression(Box<ParsedExpr<'a>>),
}
#[derive(Debug)]
pub enum TermOperator {
    Multiply,
    Divide,
}

#[derive(Debug)]
pub enum ExprOperator {
    Add,
    Subtract,
}

pub fn parse_program<'a>(input: &'a str) -> IResult<&'a str, ParsedProgram<'a>> {
    many0(preceded(
        skip_whitespace,
        alt((
            parse_declaration,
            parse_input_statement,
            parse_output_statement,
            parse_assignment,
        )),
    ))(input)
}
fn parse_input_statement<'a>(input: &'a str) -> IResult<&'a str, ParsedStatement> {
    tuple((char('>'), skip_whitespace, parse_identifier))(input)
        .map(|(rest, result)| (rest, ParsedStatement::InputOperation(result.2)))
}
fn parse_assignment(input: &str) -> IResult<&str, ParsedStatement> {
    tuple((
        parse_identifier,
        skip_whitespace,
        tag(":="),
        skip_whitespace,
        parse_expression,
    ))(input)
    .map(|(rest, result)| (rest, ParsedStatement::Assignment(result.0, result.4)))
}
fn parse_output_statement<'a>(input: &'a str) -> IResult<&'a str, ParsedStatement> {
    tuple((char('<'), skip_whitespace, parse_expression))(input)
        .map(|(rest, result)| (rest, ParsedStatement::OutputOperation(result.2)))
}
fn parse_declaration<'a>(input: &'a str) -> IResult<&'a str, ParsedStatement<'a>> {
    tuple((char('@'), skip_whitespace, parse_identifier))(input)
        .map(|(rest, result)| (rest, ParsedStatement::Declaration(result.2)))
}

fn parse_identifier<'a>(input: &'a str) -> IResult<&'a str, &'a str> {
    alpha1(input)
}

fn skip_whitespace(input: &str) -> IResult<&str, &str> {
    take_while(|ch: char| ch.is_whitespace())(input)
}

fn parse_expression(input: &str) -> IResult<&str, ParsedExpr> {
    fn parse_expr_op(input: &str) -> IResult<&str, ExprOperator> {
        alt((
            map(tag("+"), |_| ExprOperator::Add),
            map(tag("-"), |_| ExprOperator::Subtract),
        ))(input)
    }
    tuple((parse_term, many0(tuple((parse_expr_op, parse_term)))))(input)
}

fn parse_factor(input: &str) -> IResult<&str, ParsedFactor> {
    alt((
        map(parse_identifier, ParsedFactor::Identifier),
        map(double, ParsedFactor::Literal),
        map(parse_sub_expression, |expr| {
            ParsedFactor::SubExpression(Box::new(expr))
        }),
    ))(input)
}
fn parse_term(input: &str) -> IResult<&str, ParsedTerm> {
    fn parse_term_op(input: &str) -> IResult<&str, TermOperator> {
        alt((
            map(tag("*"), |_| TermOperator::Multiply),
            map(tag("/"), |_| TermOperator::Divide),
        ))(input)
    }
    tuple((parse_factor, many0(tuple((parse_term_op, parse_factor)))))(input)
}
fn parse_sub_expression(input: &str) -> IResult<&str, ParsedExpr> {
    delimited(
        preceded(skip_whitespace, char('(')),
        parse_expression,
        preceded(skip_whitespace, char(')')),
    )(input)
}
